<?php

/**
 * @version     $Id: adminsafe.php 3 2010-10-08 17:49:35Z obalyuk87@gmail.com $
 * @package     AdminSafe
 * @subpackage  Plug-ins
 * @copyright   Copyright (C) 2010 Oleksandr Balyuk (www.boolcast.com)
 * @license     http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL, see LICENSE.php
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
// no direct access
defined('_JEXEC') or die('Restricted access');
jimport('joomla.plugin.plugin');

/**
 *
 * @author Oleksandr Balyuk <obalyuk@boolcast.com>
 */
class plgSystemAdminSafe extends JPlugin {

    /**
     * Current application
     * @var JApplication 
     */
    protected $_app = null;
    /**
     * Namespace for session
     * @var string
     */
    protected $_namespace = 'adminsafe';
    /**
     * Session key for the safety flag
     * @var string
     */
    protected $_safeKey = 'safeKey';
    /**
     * Current plug-in
     * @var JPlugin
     */
    protected $_plugin = null;
    /**
     * Current plug-in parameters
     * @var JParameter
     */
    protected $_params = null;
    /**
     * Component parameters
     * @var JParameter
     */
    protected $_cParams = null;
    /**
     * Flag which determines the mode in which the plug-in is running
     * @var bool
     */
    protected $_debug = null;
    /**
     * Group identifying backend login
     * @var string
     */
    protected $_backEndGroup = 'Public Backend';
    /**
     * Path to the admin component
     * @var string
     */
    protected $_componentPath = null;
    /**
     * Secret word that user specified
     * @var string
     */
    protected $_userSecretWord = null;
    /**
     * Logger object, used to log data to the database
     * @var AdminsafeLogger
     */
    protected $_logger = null;
    /**
     * Notifier object, used to send out notifications about unsuccessful
     * Back-End access attempts
     * @var AdminsafeNotifier
     */
    protected $_notifier = null;
    /**
     * IP Checker object
     * used to make sure user's ip is not blacklisted
     * 
     * @var AdminsafeIpChecker
     */
    protected $_ipChecker = null;
    /**
     * Cookie will be stored when user logs in, the cookie will we destroyed when
     * user logs out, it will prevent from "Invalid Secret Notification" when
     * user is redirected back to Admin Login Screen after logout
     * @var string
     */
    protected $_logoutCookieName = 'as14247863895a2c4fa6df40c2d234735b';

    /**
     * Default Constructor
     *
     * @param string $subject
     * @param array $config
     */
    public function __construct(&$subject, $config = array()) {
        parent::__construct($subject, $config);

        //
        $this->_app = JFactory::getApplication();

        //
        $this->_componentPath = JPATH_ADMINISTRATOR . DS . 'components' . DS . 'com_adminsafe';
    }

    /**
     * Called after the
     * 
     * @return void
     */
    public function onAfterInitialise() {

        //  If Front-End, ignore
        if ($this->_app->isSite()) {
            return;
        }

        //  If user is logged in, ignore... do not interupt session
        if (!JFactory::getUser()->guest) {
            return;
        }

        $this->_loadPluginParams();
        $this->_debug = $this->_params->get('debug', false);


        //  Check to see if IP is banned
        $isBannedIp = $this->_isBlackListed();
        //  Check to see if Back-End is safe
        $isSafe = $isBannedIp ? false : $this->_isAdminSafe();


        //  Check Mode
        if ($this->_debug) {
            $this->_handleDebugMode($isSafe, $isBannedIp);
        } else {
            $this->_handleProductionMode($isSafe, $isBannedIp);
        }
    }

    /**
     * Handle debug mode
     *
     * @param bool $isSafe
     * @param bool $isBannedIp
     */
    protected function _handleDebugMode($isSafe, $isBannedIp) {

        //  Debug, only enque the message
        $text = ($isSafe) ? JText::_('SAFE') : JText::_('NOT SAFE');
        $type = ($isSafe) ? 'message' : 'error';
        $this->_app->enqueueMessage($text, $type);
        if (!$isSafe) {
            //
            if ($isBannedIp) {
                $this->_app->enqueueMessage('<h1>' . JText::_('Banned IP') . '</h1>', 'error');
            }

            //  Get the URL for redirection
            $url = $this->_getRedirectionUrl();

            //  Enqueue the redirection url
            $msg = JText::_('Redirecting to ') . "<br/><a href=\"$url\">$url</a>";
            $this->_app->enqueueMessage($msg, 'error');
        }
    }

    /**
     * Handles Production mode (logs and redirects)
     * 
     * @param bool $isSafe
     * @param bool $isBannedIp 
     */
    protected function _handleProductionMode($isSafe, $isBannedIp) {
        ;
        if (!$isSafe) {

            if (!$isBannedIp) {
                //  Check if action takes place after logout
                if ($this->_isAfterLogout()) {
                    //  Destroy the logout cookie
                    setcookie($this->_logoutCookieName, false, -3600);
                } else {
                    //  Handle Unsuccessful Attempt
                    $this->_handleUnsuccessfulAttempt();
                }
            }

            //  Get the URL for redirection
            $url = $this->_getRedirectionUrl();

            //  Redirect
            $this->_app->redirect($url);
        } else {

            //
            //  If AutoBanning is enabled
            if ($this->_cParams->get('enabledAutoBanning', 0)) {
                $checker = $this->_getIpChecker();
                $checker->deleteAutoBan();
            }
        }
    }

    /**
     * Checks to see if user's IP is blacklisted
     * 
     * @return bool 
     */
    protected function _isBlackListed() {

        $this->_loadComponentParams();

        //
        if ($this->_cParams->get('enforceIpBanning', 1)) {
            $checker = $this->_getIpChecker();
            return $checker->isBlackListedIpAddress();
        }

        //
        return false;
    }

    /**
     * Fired when user logs in
     *
     * @param array $user
     * @param array $options
     * @return void We do not want to return anything, because this will affect the
     * behavior of Application()->login(). We just want to observe...
     */
    public function onLoginUser($user, $options) {

        //  If Front-End, ignore
        if ($this->_app->isSite()) {
            return;
        }

        //
        $this->_loadComponentParams();

        //  If Login loggin is enabled
        if ($this->_cParams->get('logBackendLogins', 1)) {

            //  If Back-End login
            if ($options['group'] == $this->_backEndGroup) {
                $email = trim($user['email']);
                if ($email) {
                    $logger = $this->_getLogger();
                    $logger->recordUserLogin($email);
                }
            }
        }

        //
        setcookie($this->_logoutCookieName, true);
    }

    /**
     * Logs Unsuccessful Attempt to access Back-End
     */
    protected function _handleUnsuccessfulAttempt() {

        //
        $this->_loadComponentParams();

        //  Handle Logging
        //  If Login loggin is enabled
        if ($this->_cParams->get('logUnsuccessful', 1)) {

            //  Get Keyword
            $keyword = $this->_getUserEnteredKeyword();
            //
            $logger = $this->_getLogger();
            $logger->recordUnsuccessfulAdminAccess($keyword);
        }

        //  Handle IP Auto-Banning
        //
        if ($this->_cParams->get('enabledAutoBanning', 0)) {
            $checker = $this->_getIpChecker();
            if (!$checker->isWhiteListedIpAddress()) {
                $checker->flagIpAddress();
            }
        }

        //  Handle Notifications
        //
        $n1 = $this->_cParams->get('notifySuperAdmin', 1);
        $n2 = $this->_cParams->get('notifyAdmin', 0);
        $n3 = $this->_cParams->get('notifyManager', 0);
        $n4 = $this->_cParams->get('notifyCustom', null);
        if ($n1 || $n2 || $n3 || $n4) {
            $notifier = $this->_getNotifier();
            $notifier->sendNotifications();
        }
    }

    /**
     * Checks to see if user specified the correct secret word
     * 
     * @return bool true on success
     */
    protected function _isAdminSafe() {

        // Get current session
        $session = JFactory::getSession();
        $isSafe = $session->get($this->_safeKey, false, $this->_namespace);

        //  If use specified the secret before
        if ($isSafe) {
            //  Allow access
            return true;
        }

        //  Check the secret
        if ($this->_checkSecretWord()) {
            //  If secret word is correct, store flag in session to prevent
            //  future checks
            if (!$this->_debug) {
                $session->set($this->_safeKey, true, $this->_namespace);
            }
            return true;
        } else {
            //  Invalid Secret
            return false;
        }
    }

    /**
     * Checks to see if user is after logout
     * @return bool
     */
    protected function _isAfterLogout() {
        return isset($_COOKIE[$this->_logoutCookieName]);
    }

    /**
     * Checks if the specified secret matches actual secret
     * 
     * @return bool true on success
     */
    protected function _checkSecretWord() {

        //  Check if sec
        $storedSecret = $this->_params->get('secretWord', null);
        if ($this->_debug && !$storedSecret) {
            $this->_app->enqueueMessage(JText::_('SECRET WORD is empty'), 'notice');
        }
        //
        $secret = $this->_getUserEnteredKeyword();

        //
        return ($secret == $storedSecret);
    }

    /**
     * Loads plug-in parameters 
     */
    protected function _loadPluginParams() {
        //
        if (!$this->_params) {
            $this->_plugin = JPluginHelper::getPlugin('system', 'adminsafe');
            $this->_params = new JParameter($this->_plugin->params);
        }
    }

    /**
     * Loads component parameters
     */
    protected function _loadComponentParams() {
        //
        if (!$this->_cParams) {
            jimport('joomla.application.component.helper');
            $this->_cParams = JComponentHelper::getParams('com_adminsafe');
        }
    }

    /**
     * Gets ip checker object
     * Checker is used to validate User's IP Address
     *
     * @return AdminsafeLogger
     */
    protected function _getIpChecker() {

        if (!$this->_ipChecker) {
            include_once $this->_componentPath . DS . 'classes' . DS . 'AdminsafeIpChecker.php';
            $this->_ipChecker = new AdminsafeIpChecker();
        }

        return $this->_ipChecker;
    }

    /**
     * Gets logger object
     * Logger is used to record data to database
     *
     * @return AdminsafeLogger
     */
    protected function _getLogger() {

        if (!$this->_logger) {
            include_once $this->_componentPath . DS . 'classes' . DS . 'AdminsafeLogger.php';
            $this->_logger = new AdminsafeLogger();
        }

        return $this->_logger;
    }

    /**
     * Gets the notifier object
     * Notifier object is used to send out emails to user about unsuccessful login attempt
     * 
     * @return AdminsafeNotifier
     */
    protected function _getNotifier() {

        if (!$this->_notifier) {
            include_once $this->_componentPath . DS . 'classes' . DS . 'AdminsafeNotifier.php';
            $this->_notifier = new AdminsafeNotifier();
        }

        return $this->_notifier;
    }

    /**
     * Gets user secret keyword
     * 
     * @return string
     */
    protected function _getUserEnteredKeyword() {
        //
        if (!$this->_userSecretWord) {
            //
            $secret = JRequest::getString('secret', null);
            $this->_userSecretWord = ($secret) ? $secret : JFactory::getURI()->getQuery();
        }

        return $this->_userSecretWord;
    }

    /**
     * Redirects unwanted user
     */
    protected function _getRedirectionUrl() {

        //
        $redirectOption = (int) $this->_params->get('redirectOption', 0);
        $url = '';

        switch ($redirectOption) {
            case 0:
                //  Home Page
                $url = $this->_getHomePageUrl();
                break;
            case 1:
                //  Article
                $url = $this->_getSelectedArticleUrl();
                break;
            case 2:
                //  Internal Path
                $url = $this->_getCustomUrl(true);
                break;
            case 3:
                //  External Path
                $url = $this->_getCustomUrl(false);
                break;
            default :
                $url = $this->_getHomePageUrl();
        }

        return $url;
    }

    /**
     * Gets Home Page Path
     *
     * @return string
     */
    protected function _getHomePageUrl() {

        return JRoute::_(JFactory::getURI()->root(), false);
    }

    /**
     * Gets selected article path
     * 
     * @return string
     */
    protected function _getSelectedArticleUrl() {
        $aId = (int) $this->_params->get('selectedArticle', 0);
        if ($aId > 0) {
            $home = JFactory::getURI()->root(true);
            return JRoute::_("$home/index.php?option=com_content&view=article&id=$aId", false);
        } else {
            return $this->_getHomePageUrl();
        }
    }

    /**
     * Gets properly formated custom path
     *
     * @param bool $isInternal
     * @return string
     */
    protected function _getCustomUrl($isInternal=false) {
        //
        $customUrl = $this->_params->get('customPath', 'index.php');
        if ($isInternal) {
            if ($customUrl[0] != '/') {
                $customUrl = "/$customUrl";
            }
            $customUrl = JFactory::getURI()->root(true) . $customUrl;
        }

        //
        return $customUrl;
    }

}

